/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.propagation.numerical;

import ore.AttitudeLaw;
import ore.Frame;
import ore.SpacecraftState;
import ore.errors.OrekitException;
import ore.errors.PropagationException;
import ore.orbits.EquinoctialOrbit;
import ore.propagation.BoundedPropagator;
import ore.time.AbsoluteDate;

import org.apache.commons.math.ode.ContinuousOutputModel;
import org.apache.commons.math.ode.DerivativeException;
import org.apache.commons.math.ode.sampling.StepHandler;
import org.apache.commons.math.ode.sampling.StepInterpolator;

/** 
 * This class stores sequentially generated orbital parameters for
 * later retrieval.
 *
 * Instances of this class are built and then must be fed with the
 * results provided by {@link ore.propagation.Propagator Propagator}
 * objects configured in {@link
 * ore.propagation.Propagator#setEphemerisMode() ephemeris generation
 * mode}. Once propagation is done, random access to any intermediate
 * state of the orbit throughout the propagation range is possible.
 * 
 * A typical use case is for numerically integrated orbits, which can
 * be used by algorithms that need to wander around according to their
 * own purpose without cumbersome dependencies on the integrator.
 * 
 * Another use case is for persistence, as this class is serializable.
 * 
 * As this class implements the {@link ore.propagation.Propagator
 * Propagator} interface, it can itself be used in batch mode to build
 * another instance of the same type. This is however not recommended
 * since it would be a waste of resources.
 * 
 * Note that this class stores all intermediate states along with interpolation
 * models, so it may be memory intensive.
 *
 * @see ore.propagation.numerical.NumericalPropagator
 * @author Mathieu Rom&eacute;ro
 * @author Luc Maisonobe
 * @author V&eacute;ronique Pommier-Maurussane
 */
public class IntegratedEphemeris
    extends Object
    implements BoundedPropagator, ModeHandler, StepHandler
{

    /** 
     * Reference date.
     */
    private AbsoluteDate initializedReference;
    /** 
     * Frame.
     */
    private Frame initializedFrame;
    /** 
     * Central body gravitational constant.
     */
    private double initializedMu;
    /** 
     * Attitude law.
     */
    private AttitudeLaw initializedAttitudeLaw;
    /** 
     * Start date of the integration (can be min or max).
     */
    private AbsoluteDate startDate;
    /** 
     * First date of the range.
     */
    private AbsoluteDate minDate;
    /** 
     * Last date of the range.
     */
    private AbsoluteDate maxDate;
    /** 
     * Underlying raw mathematical model.
     */
    private ContinuousOutputModel model;


    /**  
     * Instance to be filled by the propagator.
     */
    public IntegratedEphemeris() {
        super();
        this.model = new ContinuousOutputModel();
    }


    /** {@inheritDoc} */
    public void initialize(AbsoluteDate reference,
                           Frame frame, double mu,
                           AttitudeLaw attitudeLaw)
    {
        this.initializedReference   = reference;
        this.initializedFrame       = frame;
        this.initializedMu          = mu;
        this.initializedAttitudeLaw = attitudeLaw;
        /*
         * Dates to be defined in last step 
         */
        this.startDate        = null;
        this.minDate          = null;
        this.maxDate          = null;
    }
    /**
     * {@inheritDoc}
     */
    public SpacecraftState propagate(AbsoluteDate date)
        throws PropagationException
    {
        try {
            if ((date.compareTo(minDate) < 0) || (date.compareTo(maxDate) > 0)) {
                throw new PropagationException("out of range date for ephemerides: {0}, [{1}, {2}]",
                                               date, minDate, maxDate);
            }
            else {
                model.setInterpolatedTime(date.durationFrom(startDate));
                double[] y = model.getInterpolatedState();

                double a    = y[0];
                double ex   = y[1];
                double ey   = y[2];
                double hx   = y[3];
                double hy   = y[4];
                double lv   = y[5];
                double mass = y[6];

                EquinoctialOrbit eq = new EquinoctialOrbit(a, ex, ey, hx, hy, lv, 2, 
                                                           initializedFrame, date, initializedMu);

                return new SpacecraftState(eq,
                                           initializedAttitudeLaw.getState(date, eq.getPositionVelocity(), 
                                                                           this.initializedFrame),
                                           mass);
            }
        }
        catch (OrekitException oe) {
            throw new PropagationException(oe.getMessage(), oe);
        }
        catch (DerivativeException de) {
            throw new PropagationException(de.getMessage(), de);
        }
    }
    /** 
     * @return The first date of the range
     */
    public AbsoluteDate getMinDate() {
        return this.minDate;
    }
    /** 
     * @return The last date of the range
     */
    public AbsoluteDate getMaxDate() {
        return this.maxDate;
    }
    /**
     * {@inheritDoc}
     */
    public void handleStep(StepInterpolator interpolator, boolean isLast)
        throws DerivativeException
    {
        this.model.handleStep(interpolator, isLast);
        if (isLast) {
            this.startDate = new AbsoluteDate(this.initializedReference, this.model.getInitialTime());
            this.maxDate   = new AbsoluteDate(this.initializedReference, this.model.getFinalTime());
            if (this.maxDate.durationFrom(this.startDate) < 0) {
                this.minDate = this.maxDate;
                this.maxDate = this.startDate;
            }
            else {
                this.minDate = this.startDate;
            }
        }
    }
    /**
     * {@inheritDoc}
     */
    public boolean requiresDenseOutput() {
        return this.model.requiresDenseOutput();
    }
    /**
     * {@inheritDoc}
     */
    public void reset() {
        this.model.reset();
    }
}
